package motiveai.tests;

import com.jme3.renderer.RenderManager;
import motiveai.components.MotiveAI;
import com.jme3.app.SimpleApplication;
import com.jme3.app.state.VideoRecorderAppState;
import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.font.BitmapFont.VAlign;
import com.jme3.font.BitmapText;
import com.jme3.font.Rectangle;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.BillboardControl;
import com.jme3.scene.shape.Box;
import com.jme3.terrain.geomipmap.TerrainLodControl;
import com.jme3.terrain.heightmap.AbstractHeightMap;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.heightmap.ImageBasedHeightMap;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
import java.util.ArrayList;
import motiveai.factors.Environment;
import motiveai.factors.EnvironmentDefinition;
import motiveai.factors.FactorBoolean;
import motiveai.learn.LearningType;

/**
 * test
 * @author oxplay
 */
public class Test2_ChestDefender extends SimpleApplication {
    
    boolean gameEnd = false;
    private MotiveAI ai;
    private TerrainQuad terrain;
    private Material mat_terrain;
    private Node npc;
    private Node player;
    
    public static void main(String[] args) {
        Test2_ChestDefender app = new Test2_ChestDefender();
        app.start();
    }
    
    private void addTerrain() {
        flyCam.setMoveSpeed(50);
        flyCam.setRotationSpeed(2f);
        /** 1. Create terrain material and load four textures into it. */
        mat_terrain = new Material(assetManager,
                "Common/MatDefs/Terrain/Terrain.j3md");

        /** 1.1) Add ALPHA map (for red-blue-green coded splat textures) */
        mat_terrain.setTexture("Alpha", assetManager.loadTexture(
                "Textures/Terrain/splat/alphamap.png"));

        /** 1.2) Add GRASS texture into the red layer (Tex1). */
        Texture grass = assetManager.loadTexture(
                "Textures/Terrain/splat/grass.jpg");
        grass.setWrap(WrapMode.Repeat);
        mat_terrain.setTexture("Tex1", grass);
        mat_terrain.setFloat("Tex1Scale", 64f);

        /** 1.3) Add DIRT texture into the green layer (Tex2) */
        Texture dirt = assetManager.loadTexture(
                "Textures/Terrain/splat/dirt.jpg");
        dirt.setWrap(WrapMode.Repeat);
        mat_terrain.setTexture("Tex2", dirt);
        mat_terrain.setFloat("Tex2Scale", 32f);

        /** 1.4) Add ROAD texture into the blue layer (Tex3) */
        Texture rock = assetManager.loadTexture(
                "Textures/Terrain/splat/road.jpg");
        rock.setWrap(WrapMode.Repeat);
        mat_terrain.setTexture("Tex3", rock);
        mat_terrain.setFloat("Tex3Scale", 128f);

        /** 2. Create the height map */
        AbstractHeightMap heightmap = null;
        Texture heightMapImage = assetManager.loadTexture(
                "Textures/Terrain/splat/mountains512.png");
        heightmap = new ImageBasedHeightMap(heightMapImage.getImage());
        heightmap.load();

        /** 3. We have prepared material and heightmap. 
         * Now we create the actual terrain:
         * 3.1) Create a TerrainQuad and name it "my terrain".
         * 3.2) A good value for terrain tiles is 64x64 -- so we supply 64+1=65.
         * 3.3) We prepared a heightmap of size 512x512 -- so we supply 512+1=513.
         * 3.4) As LOD step scale we supply Vector3f(1,1,1).
         * 3.5) We supply the prepared heightmap itself.
         */
        int patchSize = 65;
        terrain = new TerrainQuad("my terrain", patchSize, 513, heightmap.getHeightMap());

        /** 4. We give the terrain its material, position & scale it, and attach it. */
        terrain.setMaterial(mat_terrain);
        terrain.setLocalTranslation(0, 0, 0);
        terrain.setLocalScale(2f, 1f, 2f);
        rootNode.attachChild(terrain);

        /** 5. The LOD (level of detail) depends on were the camera is: */
        TerrainLodControl control = new TerrainLodControl(terrain, getCamera());
        terrain.addControl(control);
    }
    
    private Node addUser(ColorRGBA color, Vector2f position) {
        Node userNode = new Node("Node" + color);
        userNode.setLocalTranslation(position.x, 1, position.y);
        Box box1 = new Box(1, 1, 1);
        Geometry geom = new Geometry("Box" + color, box1);
        geom.setLocalTranslation(0, 0, 0);
        Material mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat1.setColor("Color", color);
        geom.setMaterial(mat1);
        MoveControl moveControl = new MoveControl();
        userNode.addControl(moveControl);
        userNode.attachChild(geom);
        rootNode.attachChild(userNode);
        BillboardControl bbControl = new BillboardControl();
        bbControl.setAlignment(BillboardControl.Alignment.Screen);
        BitmapText thinking = new BitmapText(guiFont);
        thinking.setBox(new Rectangle(0, 0, 12, 6));
        thinking.setQueueBucket(Bucket.Translucent);
        thinking.setVerticalAlignment(VAlign.Center);
        thinking.setSize(2f);
        thinking.setLocalTranslation(0, 5, 0);
        thinking.setText("initializing...");
        thinking.addControl(bbControl);
        userNode.attachChild(thinking);
        userNode.setUserData("text", thinking);
        return userNode;
    }
    
    private Vector3f getCursorContactPointWithCritter() {
        Vector3f origin = cam.getWorldCoordinates(inputManager.getCursorPosition(), 0.0f);
        Vector3f direction = cam.getWorldCoordinates(inputManager.getCursorPosition(), 0.3f);
        direction.subtractLocal(origin).normalizeLocal();
        Ray mouseRay = new Ray(origin, direction);
        CollisionResults results = new CollisionResults();
        if (rootNode != null) {
            rootNode.collideWith(mouseRay, results);
        }
        CollisionResult closestCollision = results.getClosestCollision();
        if (closestCollision != null) {
            return closestCollision.getContactPoint();
        }
        return null;
    }
    private ActionListener actionListener = new ActionListener() {
        
        public void onAction(String name, boolean keyPressed, float tpf) {
            
            if (name.equals("Click") && keyPressed) {
                Vector3f vect = getCursorContactPointWithCritter();
                player.getControl(MoveControl.class).moveTo(vect);
                System.out.println("move to: " + vect);
            }
        }
    };
    
    public void initKeys() {
        inputManager.addMapping("Click", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
        inputManager.addListener(actionListener, new String[]{"Click"});
    }
    
    private void updateYPosition(Spatial spat) {
        Vector3f vectPlayer = spat.getLocalTranslation();
        vectPlayer.y = terrain.getHeight(new Vector2f(vectPlayer.x, vectPlayer.z));
        spat.setLocalTranslation(vectPlayer);
    }
    
    public void initCharacters() {
        npc = addUser(ColorRGBA.Red, new Vector2f(0, 0));
        player = addUser(ColorRGBA.Blue, new Vector2f(-12, -12));
        npc.setUserData("health", 100);
        player.setUserData("health", 100);
        BitmapText thinking = player.getUserData("text");
        thinking.setText("player");
    }
    
    @Override
    public void simpleInitApp() {
        setDisplayFps(false);       // to hide the FPS
        setDisplayStatView(false);  // to hide the statistics 
        //stateManager.attach(new VideoRecorderAppState(1f, 30)); //start recording  
        ArrayList<Node> gameCharacters = new ArrayList<Node>();
        flyCam.setEnabled(false);
        addTerrain();
        
        Spatial spat = assetManager.loadModel("Models/chest.j3o");
        spat.setLocalTranslation(new Vector3f(0, terrain.getHeight(new Vector2f(0, 0)), 0));
        rootNode.attachChild(spat);
        
        EnvironmentDefinition env = new EnvironmentDefinition();
        env.setFactorDefinition("isEnemyClose", new FactorBoolean(false, false, 0));
        env.setFactorDefinition("isDefendingChest", new FactorBoolean(false, true, 6));
        env.setFactorDefinition("isBored", new FactorBoolean(false, false, 1));
        env.setFactorDefinition("isRested", new FactorBoolean(false, true, 1));
        env.setFactorDefinition("isAttacking", new FactorBoolean(false, true, 4));
        ai = new MotiveAI<Test2Agent>(env, LearningType.HIGH_PRECISE, 100);
        ai.registerBehaviour(Test2AttackEnemy.class);
        ai.registerBehaviour(Test2Idle.class);
        ai.registerBehaviour(Test2FindEntertainment.class);
        ai.registerBehaviour(Test2DefendTheChest.class);
        ai.setUpdateRate(0f);
        initCharacters();
        Test2Agent agent = new Test2Agent();
        agent.setSpatial(npc);
        ai.addAgent(agent);
        initKeys();
        gameCharacters.add(npc);
        gameCharacters.add(player);
        ai.addSensor(new Test2Sensor(gameCharacters));
    }
    
    @Override
    public void simpleUpdate(float tpf) {
        if (player != null) {
            updateYPosition(player);
            cam.setLocation(player.getLocalTranslation().add(new Vector3f(-20, 50, 0)));
            cam.lookAt(player.getLocalTranslation(), Vector3f.UNIT_Y);
        }
        updateYPosition(npc);
        ai.update(tpf);
        int playerHp = player.<Integer>getUserData("health");
        BitmapText thinking = player.getUserData("text");
        thinking.setText("player hp: " + playerHp);
        ///////////////////////////
        if (!gameEnd && player.getLocalTranslation().distance(new Vector3f(0, player.getLocalTranslation().y, 0)) < 2f) {
            StateWin stateWin = new StateWin(rootNode, guiNode, guiFont, cam);
            gameEnd = true;
        }
        if (!gameEnd && !player.hasAncestor(rootNode)) {
            StateGameOver stateGameOver = new StateGameOver(rootNode, guiNode, guiFont, cam);
            gameEnd = true;
        }
    }
    
    @Override
    public void simpleRender(RenderManager rm) {
        //TODO: add render code
    }
}
